﻿/******************************************************************************************************
Copyright (c) 2014 Steven Giacomelli (stevegiacomelli@gmail.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
******************************************************************************************************/

using System;
using System.Globalization;

namespace SharpGeo
{
    /// <summary>
    /// Represents a double value described by a Unit
    /// </summary>
    public struct DoubleUnitValue :
        IEquatable<DoubleUnitValue>,
        IComparable<DoubleUnitValue>
    {
        #region Constructor

        /// <summary>
        /// Creates a new DoubleUnitValue
        /// </summary>
        /// <param name="value">The value of the unit of measurement</param>
        /// <param name="unit">The unit of measurement</param>
        public DoubleUnitValue(double value, Unit unit)
        {
            if (unit == null)
            {
                throw new ArgumentNullException("unit",
                    Properties.Resources.DoubleUnitValueDoubleUnitValueArgumentNullException);
            }

            _value = value;
            _unit = unit;
        }

        #endregion Constructor

        #region Private Fields

        private Unit _unit;
        private double _value;

        #endregion Private Fields

        #region Properties

        /// <summary>
        /// The unit of measurement
        /// </summary>
        public Unit Unit { get { return _unit; } }

        /// <summary>
        /// The value of the unit of measurement
        /// </summary>
        public double Value { get { return _value; } }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Compares this DoubleUnitValue to another DoubleUnitValue
        /// </summary>
        /// <param name="other">The DoubleUnitValue to compare to</param>
        /// <returns>Returns 0 if the values are the same, -1 if this value is less than the compared value and +1 if this value is greater than the compared value</returns>
        public int CompareTo(DoubleUnitValue other)
        {
            var v = other.ConvertValueTo(_unit);

            if (_value < v)
            {
                return -1;
            }
            else if (_value > v)
            {
                return 1;
            }

            return 0;
        }

        /// <summary>
        /// Converts this DoubleUnitValue to another Unit of measurement
        /// </summary>
        /// <param name="to">The Unit of measurement to convert to</param>
        /// <returns>The converted DoubleUnitValue</returns>
        public DoubleUnitValue ConvertTo(Unit to)
        {
            return new DoubleUnitValue(ConvertValueTo(to), to);
        }

        /// <summary>
        /// Converts this value of this DoubleUnitValue to another Unit of measurement
        /// </summary>
        /// <param name="to">The Unit of measurement to convert this DoubleUnitValue to</param>
        /// <returns>The converted value</returns>
        public double ConvertValueTo(Unit to)
        {
            if (to == null)
            {
                throw new ArgumentNullException("to",
                    Properties.Resources.DoubleUnitValueConvertArgumentNullException);
            }

            if (to.BaseConversionFactor == _unit.BaseConversionFactor)
                return _value;

            return to.BaseConversionFactor / (_unit.BaseConversionFactor * _value);
        }

        /// <summary>
        /// Compares this DoubleUnitValue to another for equality
        /// </summary>
        /// <param name="obj">The DoubleUnitValue to compare to</param>
        /// <returns>Returns true if the values are equal</returns>
        public override bool Equals(object obj)
        {
            if (obj == null || !(obj is DoubleUnitValue))
                return false;

            return Equals((DoubleUnitValue)obj);
        }

        /// <summary>
        /// Compares this DoubleUnitValue to another for equality
        /// </summary>
        /// <param name="other">The DoubleUnitValue to compare to</param>
        /// <returns>Returns true if the values are equal</returns>
        public bool Equals(DoubleUnitValue other)
        {
            return _value == other.ConvertValueTo(_unit);
        }

        /// <summary>
        /// Returns the hashcode of this DoubleUnitValue
        /// </summary>
        /// <returns>The computed hashcode</returns>
        public override int GetHashCode()
        {
            return _value.GetHashCode() ^ _unit.GetHashCode();
        }

        /// <summary>
        /// Converts this DoubleUnitValue to a string
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture, "{0}{1}", Value, _unit.Abbreviation);
        }

        #endregion Methods

        #region Operators
        /// <summary>
        /// Adds the values of two DoubleUnitValue together
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue to add</param>
        /// <param name="value2">the second DoubleUnitValue to add</param>
        /// <returns>A new DoubleUnitValue resulting from the addition of the passed values returned in the Unit of value1</returns>
        /// <summary>
        public static DoubleUnitValue Add(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            return value1 + value2;
        }

        /// <summary>
        /// Decrements the passed DoubleUnitValue
        /// </summary>
        /// <param name="value">The DoubleUnitValue to decrement</param>
        /// <returns>Returns a new DoubleUnitValue that is equal to value1 - 1</returns>
        public static DoubleUnitValue Decrement(DoubleUnitValue value)
        {
            return value--;
        }

        /// <summary>
        /// Divides the values of two DoubleUnitValue together
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue to divide</param>
        /// <param name="value2">the second DoubleUnitValue to divide</param>
        /// <returns>A new DoubleUnitValue resulting from the division of the passed values returned in the Unit of value1</returns>
        public static DoubleUnitValue Divide(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            return value1 / value2;
        }

        /// <summary>
        /// Implicitly converts the passed DoubleUnitValue to a double
        /// </summary>
        /// <param name="value">The DoubleUnitValue to convert</param>
        /// <returns>The double value of the passed DoubleUnitValue</returns>
        public static implicit operator double(DoubleUnitValue value)
        {
            return value._value;
        }

        /// <summary>
        /// Increments the passed DoubleUnitValue
        /// </summary>
        /// <param name="value">The DoubleUnitValue to increment</param>
        /// <returns>Returns a new DoubleUnitValue that is equal to value1 + 1</returns>
        public static DoubleUnitValue Increment(DoubleUnitValue value)
        {
            return value++;
        }

        /// <summary>
        /// Returns the value of a modulus operation on two DoubleUnitValue
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue</param>
        /// <param name="value2">the second DoubleUnitValue</param>
        /// <returns>A new DoubleUnitValue resulting from the modulus of the passed values returned in the Unit of value1</returns>
        public static DoubleUnitValue Mod(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            return value1 % value2;
        }

        /// <summary>
        /// Multiplies the values of two DoubleUnitValue together
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue to multiply</param>
        /// <param name="value2">the second DoubleUnitValue to multiply</param>
        /// <returns>A new DoubleUnitValue resulting from the multiplication of the passed values returned in the Unit of value1</returns>
        public static DoubleUnitValue Multiply(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            return value1 * value2;
        }

        /// <summary>
        /// Negates the passed DoubleUnitValue
        /// </summary>
        /// <param name="value">The DoubleUnitValue to negate</param>
        /// <returns>The negated DoubleUnitValue</returns>
        public static DoubleUnitValue Negate(DoubleUnitValue value)
        {
            return -value;
        }

        /// <summary>
        /// Negates the passed DoubleUnitValue
        /// </summary>
        /// <param name="value">The DoubleUnitValue to negate</param>
        /// <returns>The negated DoubleUnitValue</returns>
        public static DoubleUnitValue operator -(DoubleUnitValue value)
        {
            return new DoubleUnitValue(-value._value, value._unit);
        }

        /// Subtracts the values of two DoubleUnitValue together
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue to subtract</param>
        /// <param name="value2">the second DoubleUnitValue to subtract</param>
        /// <returns>A new DoubleUnitValue resulting from the subtraction of the passed values returned in the Unit of value1</returns>
        public static DoubleUnitValue operator -(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            return new DoubleUnitValue(value1._value - value2.ConvertValueTo(value1._unit), value1._unit);
        }

        /// <summary>
        /// Decrements the passed DoubleUnitValue
        /// </summary>
        /// <param name="value">The DoubleUnitValue to decrement</param>
        /// <returns>Returns a new DoubleUnitValue that is equal to value1 - 1</returns>
        public static DoubleUnitValue operator --(DoubleUnitValue value)
        {
            return new DoubleUnitValue(value._value - 1, value.Unit);
        }

        /// <summary>
        /// Compares two DoubleUnitValue for inequality
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue to compare</param>
        /// <param name="value2">The first DoubleUnitValue to compare</param>
        /// <returns>Returns true if value1 is not equal to value2</returns>
        public static bool operator !=(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            return !value1.Equals(value2);
        }

        /// <summary>
        /// Returns the value of a modulus operation on two DoubleUnitValue
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue</param>
        /// <param name="value2">the second DoubleUnitValue</param>
        /// <returns>A new DoubleUnitValue resulting from the modulus of the passed values returned in the Unit of value1</returns>
        public static DoubleUnitValue operator %(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            return new DoubleUnitValue(value1._value % value2.ConvertValueTo(value1._unit), value1._unit);
        }

        /// <summary>
        /// Multiplies the values of two DoubleUnitValue together
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue to multiply</param>
        /// <param name="value2">the second DoubleUnitValue to multiply</param>
        /// <returns>A new DoubleUnitValue resulting from the multiplication of the passed values returned in the Unit of value1</returns>
        public static DoubleUnitValue operator *(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            return new DoubleUnitValue(value1._value * value2.ConvertValueTo(value1._unit), value1._unit);
        }

        /// <summary>
        /// Divides the values of two DoubleUnitValue together
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue to divide</param>
        /// <param name="value2">the second DoubleUnitValue to divide</param>
        /// <returns>A new DoubleUnitValue resulting from the division of the passed values returned in the Unit of value1</returns>
        public static DoubleUnitValue operator /(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            if (value2._value == 0)
            {
                throw new ArgumentException("value2.Value cannot be zero", "value2");
            }

            return new DoubleUnitValue(value1._value / value2.ConvertValueTo(value1._unit), value1._unit);
        }

        /// <summary>
        /// Adds the values of two DoubleUnitValue together
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue to add</param>
        /// <param name="value2">the second DoubleUnitValue to add</param>
        /// <returns>A new DoubleUnitValue resulting from the addition of the passed values returned in the Unit of value1</returns>
        public static DoubleUnitValue operator +(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            return new DoubleUnitValue(value1._value + value2.ConvertValueTo(value1._unit), value1._unit);
        }

        /// <summary>
        /// Increments the passed DoubleUnitValue
        /// </summary>
        /// <param name="value">The DoubleUnitValue to increment</param>
        /// <returns>Returns a new DoubleUnitValue that is equal to value1 + 1</returns>
        public static DoubleUnitValue operator ++(DoubleUnitValue value)
        {
            return new DoubleUnitValue(value._value + 1, value.Unit);
        }

        /// <summary>
        /// Compares two DoubleUnitValue with the less than operator
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue to compare</param>
        /// <param name="value2">The first DoubleUnitValue to compare</param>
        /// <returns>Returns true if value1 is less than value2</returns>
        public static bool operator <(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            return value1._value < value2.ConvertValueTo(value1._unit);
        }

        /// <summary>
        /// Compares two DoubleUnitValue with the less than or equal operator
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue to compare</param>
        /// <param name="value2">The first DoubleUnitValue to compare</param>
        /// <returns>Returns true if value1 is less than or equal to value2</returns>
        public static bool operator <=(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            return value1._value <= value2.ConvertValueTo(value1._unit);
        }

        /// <summary>
        /// Compares two DoubleUnitValue for equality
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue to compare</param>
        /// <param name="value2">The first DoubleUnitValue to compare</param>
        /// <returns>Returns true if value1 is equal to value2</returns>
        public static bool operator ==(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            return value1.Equals(value2);
        }

        /// <summary>
        /// Compares two DoubleUnitValue with the greater than operator
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue to compare</param>
        /// <param name="value2">The first DoubleUnitValue to compare</param>
        /// <returns>Returns true if value1 is greater than value2</returns>
        public static bool operator >(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            return value1._value > value2.ConvertValueTo(value1._unit);
        }

        /// <summary>
        /// Compares two DoubleUnitValue with the greater than or equal operator
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue to compare</param>
        /// <param name="value2">The first DoubleUnitValue to compare</param>
        /// <returns>Returns true if value1 is greater than or equal to value2</returns>
        public static bool operator >=(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            return value1._value >= value2.ConvertValueTo(value1._unit);
        }

        /// <summary>
        /// Subtracts the values of two DoubleUnitValue together
        /// </summary>
        /// <param name="value1">The first DoubleUnitValue to subtract</param>
        /// <param name="value2">the second DoubleUnitValue to subtract</param>
        /// <returns>A new DoubleUnitValue resulting from the subtraction of the passed values returned in the Unit of value1</returns>
        public static DoubleUnitValue Subtract(DoubleUnitValue value1, DoubleUnitValue value2)
        {
            return value1 - value2;
        }

        /// <summary>
        /// Converts this DoubleUnitValue to a double
        /// </summary>
        /// <returns>The double value of the passed DoubleUnitValue</returns>
        public double ToDouble()
        {
            return _value;
        }

        #endregion Operators
    }
}